

#include "base/input_output_context.h"
#include "base/base_util.h"
#include "codec_config.h"
#include "dump.h"
#include "message_queue.h"
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <libavformat/avformat.h>
#include <libavutil/log.h>
#include <libavutil/pixfmt.h>
#include <strings.h>
#include <thread>
#include <ctime>
#include <unistd.h>
#include <sys/time.h>

struct MessageQueues
{
    PacketMessageQueue m_packet_queue;
    FrameMessageQueue  m_frame_queue;
    PacketMessageQueue m_encoder_packet_queue;
};

void ProcessPacketData(InputFileData * input_data, OutputFileData * output_data, MessageQueues & message_queue)
{
    int result = 0;
    AVPacket * pkt = nullptr;
    AVFrame  * frame = nullptr;
    while (true)
    {
        pkt = nullptr;
        result = message_queue.m_packet_queue.PopPacketNonBlocking(&pkt);
        if (result < 0)
        {
            if (result == AVERROR_EOF)
            {
                break;
            }
            continue;
        }
        if (input_data->m_video_stream_index == pkt->stream_index)
        {
            DecoderPacketData(input_data->m_video_decoder_ctx, pkt, message_queue.m_frame_queue);
            while (true)
            {
                frame = nullptr;
                result = message_queue.m_frame_queue.PopFrameNonBlocking(&frame);
                if (result < 0)
                {
                    break;
                }

                // PrintFrameInfo(input_data->m_video_decoder_ctx, frame);

                EncoderFrameData(output_data->m_video_encoder_ctx, frame, message_queue.m_encoder_packet_queue);

                while (true)
                {
                    AVPacket * packet = nullptr;
                    result = message_queue.m_encoder_packet_queue.PopPacketNonBlocking(&packet);
                    if (result < 0)
                    {
                        break;
                    }
                    printf("vidoe pts : %8ld\tdts : %8ld\tduration : %8ld.\n", packet->pts, packet->dts, packet->duration);

                    av_packet_free(&packet);
                }
                av_frame_free(&frame);
            }
        }
        else
        {
            DecoderPacketData(input_data->m_audio_decoder_ctx, pkt, message_queue.m_frame_queue);
            while (true)
            {
                frame = nullptr;
                result = message_queue.m_frame_queue.PopFrameNonBlocking(&frame);
                if (result < 0)
                {
                    break;
                }

                // PrintFrameInfo(input_data->m_audio_decoder_ctx, frame);

                EncoderFrameData(output_data->m_audio_encoder_ctx, frame, message_queue.m_encoder_packet_queue);

                while (true)
                {
                    AVPacket * packet = nullptr;
                    result = message_queue.m_encoder_packet_queue.PopPacketNonBlocking(&packet);
                    if (result < 0)
                    {
                        break;
                    }
                    printf("audio pts : %8ld\tdts : %8ld\tduration : %8ld.\n", packet->pts, packet->dts, packet->duration);
                    av_packet_free(&packet);
                }

                av_frame_free(&frame);
            }
        }
        av_packet_free(&pkt);
    }
    ProcessNullptrPacket(input_data->m_video_decoder_ctx, message_queue.m_frame_queue);

    while (true)
    {
        frame = nullptr;
        result = message_queue.m_frame_queue.PopFrameNonBlocking(&frame);
        if (result < 0)
        {
            break;
        }
        EncoderFrameData(output_data->m_video_encoder_ctx, frame, message_queue.m_encoder_packet_queue);
        while (true)
        {
            pkt = nullptr;
            result = message_queue.m_encoder_packet_queue.PopPacketNonBlocking(&pkt);
            if (result < 0)
            {
                break;
            }
            av_packet_free(&pkt);
        }
        av_frame_free(&frame);
    }
    printf("finish.\n");
}

int main()
{

    av_log_set_level(AV_LOG_INFO);

    std::string filename = "";

    InputFileData * input_data = new InputFileData();

    bool result = input_data->OpenFile(filename);
    // result = input_data->InitDecoderCtxWithHwName("qsv");
    result = input_data->InitDecoderCtx();
    if (!result)
    {
        delete input_data;
        input_data = nullptr;
        return EXIT_FAILURE;
    }
    OutputFileData * output_data = new OutputFileData();

    CodecConfig codec_config;
    // codec_config.SetHasBFrame(0);
    codec_config.SetCodecName("libx264");

    output_data->InitOutputFmtCtx("./test.mp4", nullptr);
    result = output_data->CreateVideoNewStream(input_data, "", &codec_config);
    if (!result)
    {
        delete input_data;
        input_data = nullptr;
        delete output_data;
        output_data = nullptr;
        return EXIT_FAILURE;
    }


    output_data->CreateAudioNewStream(input_data);

    MessageQueues message_queue;
    message_queue.m_packet_queue.InitMessageQueue(100);
    message_queue.m_frame_queue.InitMessageQueue(100);
    message_queue.m_encoder_packet_queue.InitMessageQueue(1000);

    // av_dump_format(output_data->m_output_fmt_ctx, 0, output_data->m_output_url.c_str(), 1);

    struct timeval begin;
    bzero(&begin, sizeof(begin));

    gettimeofday(&begin, nullptr);

    auto thd = std::thread([input_data, &message_queue](){


        ReadFrameToQueue(input_data->m_format_ctx, input_data->m_read_packet, message_queue.m_packet_queue);

    });

    ProcessPacketData(input_data, output_data, message_queue);

    if (thd.joinable())
    {
        thd.join();
    }

    struct timeval end_time;
    bzero(&end_time, sizeof(end_time));

    gettimeofday(&end_time, nullptr);

    printf("time : %ld:%ld.\n", (end_time.tv_sec - begin.tv_sec), std::abs(end_time.tv_usec - begin.tv_usec));

    delete input_data;
    delete output_data;
    return EXIT_SUCCESS;
}
